# -*- coding: utf-8 -*-
#Build with Python 2.7.15, pygame 1.9.6.
#运行本脚本需要系统支持黑体字体,或者你可以修改snake_init.py中的TEXT_FONT全局变量来切换字体
#棋盘界面的贴图分为三层，第一层是空地贴图，每个格子都有；第二层是轨道（分充电已/未完成）；第三层是列车，乘客。
#格子编号对应关系：1空地，2横向轨道，3纵向轨道，4二维轨道
#方向:UDLR

import sys
import os
from time import sleep
from timeit import default_timer
from datetime import datetime
from random import randint
import pygame
from pygame.locals import *

from snake_init import *

global clock
clock = pygame.time.Clock()

global window

global IMAGE_SET
IMAGE_SET = {}#set named, map however

global WINDOW_X, WINDOW_Y
WINDOW_X = 800
WINDOW_Y = 600

global SCALE
SCALE = 30

global RED, GREEN, BLUE, BLACK, WHITE
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)

global fps#暂无意义


def cls():
    window.fill(BLACK)

def drawText(textContent, xpix, ypix, textSize = 30, textColor = WHITE):
    textFont = pygame.font.SysFont(TEXT_FONT, textSize)
    textSurface = textFont.render(textContent, False, textColor)
    window.blit(textSurface, (xpix, ypix))

def drawImage(img, xpixleft, ypixup):
    window.blit(img, (xpixleft, ypixup) )

def checkQuitEvent(event):
    if event.type == pygame.QUIT:
        sys.exit()
        
def getOppositeDirection(direction):
    if direction == 'U': return 'D'
    elif direction == 'D': return 'U'
    elif direction == 'L': return 'R'
    elif direction == 'R': return 'L'
    

class Train():
    
    def __init__(self, initNode = (0, 0), moveDirection = 'R'):
        self.nodes = [ (initNode) ]
        self.moveDirection = moveDirection
        self.length = 1
        self.power = INIT_POWER
        
    def addNode(self):
        
        headNode = self.nodes[self.length - 1]
        
        if self.moveDirection == 'U': self.nodes.append( (headNode[0], headNode[1] - 1) )
        elif self.moveDirection == 'D': self.nodes.append( (headNode[0], headNode[1] + 1) )
        elif self.moveDirection == 'L': self.nodes.append( (headNode[0] - 1, headNode[1]) )
        elif self.moveDirection == 'R': self.nodes.append( (headNode[0] + 1, headNode[1]) )
        
        self.length += 1
        
    def delNode(self):
    
        node = self.nodes[0]
        del self.nodes[0]
        
        self.length -= 1
        
        return node
        
    def drawTrain(self):
        
        #绘制车头，朝向同运动方向
        drawImage(IMAGE_SET['train-head-' + self.moveDirection], self.nodes[self.length - 1][0] * SCALE, self.nodes[self.length - 1][1] * SCALE)
        
        #有车尾时绘制车尾
        if self.length >= 2:
            drawImage(IMAGE_SET['train-head-' + self.judgeRelaPos(self.nodes[0], self.nodes[1])], self.nodes[0][0] * SCALE, self.nodes[0][1] * SCALE )
            
            #有车身时绘制车身
            if self.length >= 3:
                for index in xrange(1, self.length - 1):
                
                    relForward = self.judgeRelaPos(self.nodes[index], self.nodes[index + 1])
                    relBackward = self.judgeRelaPos(self.nodes[index], self.nodes[index - 1])
                    imgType = ''
                    
                    if relForward in 'LR' and relBackward in 'LR':
                        imgType = 'train-body-LR-straight'
                    elif relForward in 'UD' and relBackward in 'UD':
                        imgType = 'train-body-UD-straight'
                    elif (relForward in 'LD' and relBackward in 'LD') or (relForward in 'RU' and relBackward in 'RU'):
                        imgType = 'train-body-DR-UL-curly'
                    elif (relForward in 'RD' and relBackward in 'RD') or (relForward in 'LU' and relBackward in 'LU'):
                        imgType = 'train-body-DL-UR-curly'
                        
                    drawImage(IMAGE_SET[imgType], self.nodes[index][0] * SCALE, self.nodes[index][1] * SCALE)
                        
        
    def judgeRelaPos(self, d, s):
        #判断d关于s的方位，要求s、d相邻
        #s、d均为node
        
        if s[0] == d[0]:
            if s[1] == d[1] + 1: return 'U'
            return 'D'
        elif s[0] == d[0] + 1: return 'L'
        return 'R'
            

'''
class Node():
    
    def __init__(self, xpos, ypos):
        self.xpos = xpos
        self.ypos = ypos
'''


#列车碰撞一个乘客时，并不是清除该实例并创建一个新实例，而是将原实例位置刷新“视为”一个新实例
class Passenger():

    def __init__(self):
        self.xpos = 0
        self.ypos = 0

    def spawnNewPos(self):

        self.xpos = randint(0, 19)
        self.ypos = randint(0, 19)

    def checkSpawnCollision(self, train):
        #检验乘客生成时是否与列车重合
        return (self.xpos, self.ypos) in train.nodes

    def drawPassenger(self):
        drawImage(IMAGE_SET['passenger'], self.xpos * SCALE, self.ypos * SCALE)


class Map():
    
    def __init__(self, mapType = 0):
        
        with open('map' + os.sep + str(mapType) + '.dat', 'rU') as file:
            
            self.specBlocks = []
            specBlocks = file.readlines()
            for item in specBlocks:
                
                block = item.split(' ')
                self.specBlocks.append( Block(int(block[0]), int(block[1]), int(block[2])) )
                
    def rechargeBlocksIfReady(self):

        for block in self.specBlocks:
            block.rechargeIfReady()

    def drawMap(self):
        
        for block in self.specBlocks:
            block.drawBlock()
            
    
#对于空地不做记录，仅记录非空地的格子
class Block():
    
    def __init__(self, type, xpos, ypos):
        self.type = type
        self.xpos = xpos
        self.ypos = ypos
        self.isCharged = True
        self.lastDischargeTime = 0

    def discharge(self):
        self.isCharged = False
        self.lastDischargeTime = default_timer()

    def rechargeIfReady(self):
        if default_timer() - self.lastDischargeTime >= CHARGE_TIME_COST:
            self.isCharged = True
            #print 'run'#debug

    def drawBlock(self):

        imgType = ''

        if self.type == 1: imgType = 'railway-LR'
        elif self.type == 2: imgType = 'railway-UD'
        elif self.type == 3: imgType = 'railway-full'

        if self.isCharged == True: imgType += '-powered'#充电的格子一律加'-powered'后缀

        drawImage(IMAGE_SET[imgType], self.xpos * SCALE, self.ypos * SCALE)



#游戏主要有五个界面
class Game():


    def __init__(self):
        self.trainType = 0#0 for 和谐号(CRH), 1 for 复兴号(CR)
        self.mapType = 0#0 for 吉安, 1 for 衡阳, 2 for 郑州
        self.mode = 0#0 for 无限模式, 1 for 加急模式
        self.fps = {0 : CRH_FPS, 1 : CR_FPS}

    def showMainMenu(self):

        cls()
        pygame.display.update()

        drawText(u'贪吃蛇', 250, 0, textSize = 100)
        drawText(u'进入游戏(按r)', 250, 150, textSize = 50, textColor = RED)
        drawText(u'查看排行(按f)', 250, 250, textSize = 50, textColor = GREEN)
        drawText(u'退出程序(按v)', 250, 350, textSize = 50, textColor = BLUE)
        
        pygame.display.update()

        while True:

            for event in pygame.event.get():
                checkQuitEvent(event)#关闭窗口退出游戏
                if event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_r:#r键进入关卡配置界面
                        self.showSelectMenu()
                        return
                    elif event.key == pygame.K_f:#f进入排行榜界面
                        self.showScorePage()
                        return
                    elif event.key == pygame.K_v:#v键退出游戏
                        sys.exit()

                    


    def showSelectMenu(self):

        cls()
        pygame.display.update()

        self.trainType = 0
        self.mapType = 0
        self.mode = 0
        chooseCur = 0

        while True:

            cls()
            drawText(u'选择配置', 200, 0, textSize = 100)
            drawText(u'依次输入相应配置对应的数字将选择对应的配置', 0, 100)
            drawText(u'按y确认配置并进入游戏(未选择的配置视为自动选择第一项)', 0, 130)
            drawText(u'车型:', 50, 200), drawText(u'和谐号0', 200, 200), drawText(u'复兴号1', 400, 200)
            drawText(u'地图:', 50, 300), drawText(u'吉安0', 200, 300), drawText(u'衡阳1', 400, 300), drawText(u'郑州2', 600, 300)
            drawText(u'模式:', 50, 400), drawText(u'无限模式0', 200, 400), drawText(u'加急模式1', 400, 400)
            
            #游标指向，选择配置高亮
            #chooseCur的有效值为0,1,2
            if chooseCur in range(0, 3): drawText('=>', 0, 200 + chooseCur * 100)

            if self.trainType == 0: drawText(u'和谐号0', 200, 200, textColor = RED)
            else: drawText(u'复兴号1', 400, 200, textColor = RED)
            if self.mapType == 0: drawText(u'吉安0', 200, 300, textColor = RED)
            elif self.mapType == 1: drawText(u'衡阳1', 400, 300, textColor = RED)
            else: drawText(u'郑州2', 600, 300, textColor = RED)
            if self.mode == 0: drawText(u'无限模式0', 200, 400, textColor = RED)
            else: drawText(u'加急模式1', 400, 400, textColor = RED)

            pygame.display.update()

            for event in pygame.event.get():

                checkQuitEvent(event)#关闭窗口退出游戏

                if event.type == pygame.KEYDOWN:

                    if event.key == pygame.K_y:#确认选择进入游戏
                        self.showGamePage()
                        return

                    elif chooseCur == 0:#选择车型
                        chooseCur = 1
                        if event.key == pygame.K_0: self.trainType = 0#和谐号
                        elif event.key == pygame.K_1: self.trainType = 1#复兴号

                    elif chooseCur == 1:
                        chooseCur = 2
                        if event.key == pygame.K_0: self.mapType = 0
                        elif event.key == pygame.K_1: self.mapType = 1
                        elif event.key == pygame.K_2: self.mapType = 2

                    elif chooseCur == 2:
                        chooseCur = 0
                        if event.key == pygame.K_0: self.mode = 0
                        elif event.key == pygame.K_1: self.mode = 1

            clock.tick(30)

    def showGamePage(self):

        
        #初始化开始
        cls()
        pygame.display.update()
        
        #初始化参量，实例
        score = 0
        fps = self.fps[self.trainType]
        map = Map(self.mapType)
        train = Train()
        passenger = Passenger()
        passenger.spawnNewPos()
        while passenger.checkSpawnCollision(train):
            passenger.spawnNewPos()
        
        #初始化视图界面
        self.drawBar(score, train.power, 0)
        map.drawMap()
        train.drawTrain()
        passenger.drawPassenger()
        pygame.display.update()
        
        #初始化计时器
        allStartTimer = default_timer()

        #载入音乐
        pygame.mixer.music.load('music' + os.sep + 'express-run.mid')
        pygame.mixer.music.play(-1)

        #初始化完成
        
        while True:

            curStartTimer = default_timer()

            fromDirection = getOppositeDirection(train.moveDirection)
            
            map.rechargeBlocksIfReady()
            
            #判断死亡
            
            isDead = False

            headNode = train.nodes[train.length - 1]
            #车头撞其他部位，超出界限
            if headNode in train.nodes[0: (train.length - 2)] or headNode[0] not in range(20) or headNode[1] not in range(20): isDead = True
            #入轨判死
            for block in map.specBlocks:
                if headNode[0] == block.xpos and headNode[1] == block.ypos:
                    if (block.type == 1 and train.moveDirection in 'UD') or (block.type == 2 and train.moveDirection in 'LR'): isDead = True

                    if block.isCharged == True:
                        train.power += CHARGE_POWER
                        if train.power > MAX_POWER: train.power = MAX_POWER
                        block.discharge() 
            
            #监听事件，读取键盘输入执行对应转向操作
            for event in pygame.event.get():

                checkQuitEvent(event)#关闭窗口退出游戏

                if event.type == pygame.KEYDOWN and train.power > 0:#列车断电后无法转向
                    
                    toDirection = ''
                    if event.key == pygame.K_UP: toDirection = 'U'
                    elif event.key == pygame.K_DOWN: toDirection = 'D'
                    elif event.key == pygame.K_LEFT: toDirection = 'L'
                    elif event.key == pygame.K_RIGHT: toDirection = 'R'
                    
                    if toDirection != '' and toDirection != fromDirection:
                        train.moveDirection = toDirection

            #出轨判死，防止轨内非法方向运动
            for block in map.specBlocks:
                if headNode[0] == block.xpos and headNode[1] == block.ypos:
                    if (block.type == 1 and train.moveDirection in 'UD') or (block.type == 2 and train.moveDirection in 'LR'): isDead = True
            
            if isDead == True:
                drawText(u'道路千万条', 0, 140, textSize = 160, textColor = RED)
                drawText(u'安全第一条', 0, 300, textSize = 160, textColor = RED)
                pygame.display.update()
                clock.tick(1)
                
                if self.mode == 0: self.showDeathPage(score)
                elif self.mode == 1: self.showDeathPage(int(default_timer() - allStartTimer) )
                
                pygame.mixer.music.fadeout(1)
                return

            #前进
            train.addNode()
            if train.power > 0: train.power -= 1

            #判载（撞）人
            if passenger.checkSpawnCollision(train) == False: #未碰撞
                train.delNode()
            else:
                passenger.spawnNewPos()
                while passenger.checkSpawnCollision(train):
                    passenger.spawnNewPos()
                score += 1

            #依次绘制地图，（刷新）列车，同时绘制状态栏
            cls()
            self.drawBar(score, train.power, allStartTimer)
            map.drawMap()
            train.drawTrain()
            passenger.drawPassenger()
            pygame.display.update()
            clock.tick(fps)

            #加急模式下判断完成任务
            if self.mode == 1 and score >= MISSION_SCORE:
                drawText(u'任务完成', 80, 220, textSize = 160, textColor = RED)
                pygame.display.update()
                clock.tick(1)
                
                self.showDeathPage(int(default_timer() - allStartTimer), True)
                
                pygame.mixer.music.fadeout(1)
                return

            #print default_timer() - curStartTimer #debug
            #补齐帧率
            if default_timer() - curStartTimer < (1 / fps):
                sleep(1 / fps - (default_timer() - curStartTimer) )
            #print default_timer() - curStartTimer #debug
            

    def showDeathPage(self, score, isMissionDone = False):
        #显示死亡信息属于showGamePage的职能，此函数只负责死亡后跳转到的登记得分页面相关内容
        
        cls()
        pygame.display.update()

        with open('scores.dat', 'r+') as file:
            scores = file.readlines()

        startIndex = scores.index('#' + str(self.trainType) + str(self.mapType) + str(self.mode) + '\n')
        endIndex = len(scores) - 1#对于#121配置,恰好设置文件末的空行对应endIndex
        for item in scores[(startIndex + 1) :]:
            if item[0] == '#':
                endIndex = scores.index(item)
                break
        
        curIndex = endIndex
        isCurChanged = False
        isfull = bool( (endIndex - startIndex) == 6 )

        #当当前配置下数据未满时，平行的成绩应当被登记；反之不登记
        for item in scores[(endIndex - 1) : startIndex: -1]:
            if ( self.mode == 0 and score + (not isfull) > int(item.split(',')[0]) ) or ( self.mode == 1 and score - (not isfull) < int(item.split(',')[0][:(len(item.split(',')[0]) - 2 )] )):
                curIndex = scores.index(item)
                isCurChanged = True

        #加急模式下，任务完成才可计入排行
        if (self.mode == 0 or isMissionDone == True) and (isCurChanged == True or isfull == False):#本次数据达到插入条件
            
            #按格式生成当前时间
            dateTime = datetime.now()
            curMinute = str(dateTime.minute)#str
            if len(curMinute) == 1: curMinute = '0' + curMinute

            currentTime = str(dateTime.year) + '/' + str(dateTime.month) + '/' + str(dateTime.day) + ' ' + str(dateTime.hour) + ':' + curMinute
            
            #插入本次游戏数据至排行
            if self.mode == 0: scores.insert(curIndex, str(score) + ',' + currentTime + '\n')
            elif self.mode == 1: scores.insert(curIndex, str(score) + 's,' + currentTime + '\n')
            
            if isfull == True: del scores[endIndex]#数据量溢出时删除当前最差数据
            
            drawText(u'您本次的成绩刷新了排行情况！', 200, 300, textColor = RED)

        with open('scores.dat', 'w') as file:
            file.writelines(scores)

        #print scores#debug

        #显示提示内容，等待返回主界面
        if self.mode == 0: drawText(u'本次成绩:%s' % str(score), 200, 250)
        elif self.mode == 1: drawText(u'本次成绩:%ss' % str(score), 200, 250)
        drawText(u'按n返回主界面', 200, 350)
        
        pygame.display.update()

        while True:
            
             for event in pygame.event.get():

                checkQuitEvent(event)#关闭窗口退出游戏

                if event.type == pygame.KEYDOWN:

                    if event.key == pygame.K_n:#回到主界面
                        return

    def showScorePage(self):
        #对于每种配置，只记录前五的分数

        cls()
        pygame.display.update()

        with open('scores.dat', 'rU') as file:
            scores = file.read().split('#')
            for index in xrange(len(scores) ): scores[index] = scores[index].split('\n')
            #如是得到一个二维列表,scores[0][i]无意义,scores[i(i!=0)][0]指示配置    

        self.trainType = 0
        self.mapType = 0
        self.mode = 0
        chooseCur = 0

        #print scores#debug

        while True:

            cls()
            drawText(u'选择配置', 200, 0, textSize = 100)
            drawText(u'依次输入相应配置对应的数字将选择对应的配置', 0, 100)
            drawText(u'根据所选配置自动显示对应得分排行数据', 0, 130)
            drawText(u'按n回到主界面', 0, 160)
            drawText(u'车型:', 50, 200), drawText(u'和谐号0', 200, 200), drawText(u'复兴号1', 400, 200)
            drawText(u'地图:', 50, 230), drawText(u'吉安0', 200, 230), drawText(u'衡阳1', 400, 230), drawText(u'郑州2', 600, 230)
            drawText(u'模式:', 50, 260), drawText(u'无限模式0', 200, 260), drawText(u'加急模式1', 400, 260)
            drawText(u'位次', 0, 300), drawText(u'时间', 100, 300), drawText(u'成绩', 500, 300)

            #游标指向，选择配置高亮
            #chooseCur的有效值为0,1,2
            if chooseCur in range(0, 3): drawText('=>', 0, 200 + chooseCur * 30)

            if self.trainType == 0: drawText(u'和谐号0', 200, 200, textColor = RED)
            else: drawText(u'复兴号1', 400, 200, textColor = RED)
            if self.mapType == 0: drawText(u'吉安0', 200, 230, textColor = RED)
            elif self.mapType == 1: drawText(u'衡阳1', 400, 230, textColor = RED)
            else: drawText(u'郑州2', 600, 230, textColor = RED)
            if self.mode == 0: drawText(u'无限模式0', 200, 260, textColor = RED)
            else: drawText(u'加急模式1', 400, 260, textColor = RED)

            indexCur = 1 + self.trainType * 6 + self.mapType * 2 + self.mode
            for index in xrange(1, len(scores[indexCur]) - 1):
                #if not scores[indexCur][index]: continue#舍去split()产生的''项
                ypix = 300 + 50 * index
                drawText(str(index), 0, ypix)
                drawText(scores[indexCur][index].split(',')[1], 100, ypix)#日期在左
                drawText(scores[indexCur][index].split(',')[0], 500, ypix)#成绩在右

            pygame.display.update()


            for event in pygame.event.get():

                checkQuitEvent(event)#关闭窗口退出游戏

                if event.type == pygame.KEYDOWN:

                    if event.key == pygame.K_n:#回到主界面
                        return

                    elif chooseCur == 0:#选择车型
                        chooseCur = 1
                        if event.key == pygame.K_0: self.trainType = 0#和谐号
                        elif event.key == pygame.K_1: self.trainType = 1#复兴号

                    elif chooseCur == 1:
                        chooseCur = 2
                        if event.key == pygame.K_0: self.mapType = 0
                        elif event.key == pygame.K_1: self.mapType = 1
                        elif event.key == pygame.K_2: self.mapType = 2

                    elif chooseCur == 2:
                        chooseCur = 0
                        if event.key == pygame.K_0: self.mode = 0
                        elif event.key == pygame.K_1: self.mode = 1

            clock.tick(30)
            
    def drawBar(self, score, power, startTimer):
    
        pygame.draw.rect(window, WHITE, (600, 0, 20, 600), 0)
        
        drawText('Score:', 630, 200)
        drawText(str(score), 630, 240)

        textColor = GREEN
        split_max_power = MAX_POWER / 3
        drawText('Power:', 630, 300)
        if split_max_power < power <= (split_max_power * 2): textColor = WHITE
        elif 0 <= power <= split_max_power: textColor = RED
        drawText(str(power), 630, 340, textColor = textColor)

        if self.mode == 1: 
            drawText('Time:', 630, 400)
            drawText(str(int(default_timer() - startTimer) ) + 's', 630, 440)


if __name__ == '__main__':


    #将工作路径切换到脚本所在路径，确保其他文件能正常读取
    os.chdir(sys.path[0])

    pygame.init()
        
    for fileName in os.listdir('image'):
        IMAGE_SET[fileName.split('.')[0] ] = pygame.image.load('image' + os.sep + '%s' % fileName)

    window = pygame.display.set_mode( (WINDOW_X, WINDOW_Y), 0, 32)
    game = Game()

    while True:
        game.showMainMenu()

    



